package com.example.denglu.util;


import com.fasterxml.jackson.databind.BeanProperty;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 基于CGlib
 * 实体工具类，目前copy不支持map、list
 *
 * @author zhang
 * @date 2017年4月26日下午5:10:42
 */
public final class BeanUtils extends org.springframework.beans.BeanUtils {
    private BeanUtils() {
    }


    /**
     * 实例化对象
     *
     * @param clazz 类
     * @return 对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T newInstance(Class<?> clazz) {
        return (T) instantiate(clazz);
    }

    /**
     * 实例化对象
     *
     * @param clazzStr 类名
     * @return 对象
     */
    public static <T> T newInstance(String clazzStr) {
        try {
            Class<?> clazz = Class.forName(clazzStr);
            return newInstance(clazz);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取Bean的属性
     *
     * @param bean         bean
     * @param propertyName 属性名
     * @return 属性值
     */
    public static Object getProperty(Object bean, String propertyName) {
        PropertyDescriptor pd = getPropertyDescriptor(bean.getClass(), propertyName);
        if (pd == null) {
            throw new RuntimeException("Could not read property '" + propertyName + "' from bean PropertyDescriptor is null");
        }
        Method readMethod = pd.getReadMethod();
        if (readMethod == null) {
            throw new RuntimeException("Could not read property '" + propertyName + "' from bean readMethod is null");
        }
        if (!readMethod.isAccessible()) {
            readMethod.setAccessible(true);
        }
        try {
            return readMethod.invoke(bean);
        } catch (Throwable ex) {
            throw new RuntimeException("Could not read property '" + propertyName + "' from bean", ex);
        }
    }

    /**
     * 设置Bean属性
     *
     * @param bean         bean
     * @param propertyName 属性名
     * @param value        属性值
     */
    public static void setProperty(Object bean, String propertyName, Object value) {
        PropertyDescriptor pd = getPropertyDescriptor(bean.getClass(), propertyName);
        if (pd == null) {
            throw new RuntimeException("Could not set property '" + propertyName + "' to bean PropertyDescriptor is null");
        }
        Method writeMethod = pd.getWriteMethod();
        if (writeMethod == null) {
            throw new RuntimeException("Could not set property '" + propertyName + "' to bean writeMethod is null");
        }
        if (!writeMethod.isAccessible()) {
            writeMethod.setAccessible(true);
        }
        try {
            writeMethod.invoke(bean, value);
        } catch (Throwable ex) {
            throw new RuntimeException("Could not set property '" + propertyName + "' to bean", ex);
        }
    }

    /**
     * 给一个Bean添加字段
     *
     * @param superBean 父级Bean
     * @param props     新增属性
     * @return {Object}
     */
    public static Object generator(Object superBean, BeanProperty... props) {
        Class<?> superclass = superBean.getClass();
        Object genBean = generator(superclass, props);
        BeanUtils.copy(superBean, genBean);
        return genBean;
    }

    public static MultiValueMap<String, String> object2Map(Object obj) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        if (obj == null) {
            return map;
        }
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.get(obj) != null)
                    map.add(field.getName(), field.get(obj).toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return map;
    }

    /**
     * copy 对象属性到另一个对象，默认不使用Convert
     *
     * @param src
     * @param clazz 类名
     * @return T
     */
    public static <T> T copy(Object src, Class<T> clazz) {
        T to = newInstance(clazz);
        if (src != null) {
            BeanCopier copier = BeanCopier.create(src.getClass(), clazz, false);
            copier.copy(src, to, null);
        }
        return to;
    }

    /**
     * 将一个对象列表，转换成另一个对象的列表
     *
     * @param srcList
     * @param clazz
     * @param <S>
     * @param <T>
     * @return
     */
    public static <S, T> List<T> copy(List<S> srcList, Class<T> clazz) {
        return Optional.ofNullable(srcList).isPresent() ? srcList.stream().map(srcObj -> copy(srcObj, clazz)).collect(Collectors.toList()) : new ArrayList<T>();
    }

    /**
     * 拷贝对象
     *
     * @param src  源对象
     * @param dist 需要赋值的对象
     */
    public static void copy(Object src, Object dist) {
        BeanCopier copier = BeanCopier
                .create(src.getClass(), dist.getClass(), false);

        copier.copy(src, dist, null);
    }

    /**
     * 通过反射转换对象。
     *
     * @param source      源对象
     * @param targetClass 目标类
     * @return 目标对象
     */
    public static <T, R> R convert(T source, Class<R> targetClass) {
        if (source == null) {
            return null;
        }
        R target = null;
        try {
            target = targetClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            //throw Errors.wrap(e);
            throw new RuntimeException(e);
        }
        copyProperties(source, target);
        return target;
    }

    /**
     * 通过反射转换对象并忽略其中一些属性。
     *
     * @param source            源对象
     * @param targetClass       目标类
     * @param ignoredProperties 不复制这些属性
     * @return 目标对象
     */
    public static <T, R> R convert(T source, Class<R> targetClass, String... ignoredProperties) {
        if (source == null) {
            return null;
        }
        R target = null;
        try {
            target = targetClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            //throw Errors.wrap(e);
            throw new RuntimeException(e);
        }
        copyProperties(source, target, ignoredProperties);
        return target;
    }

    /**
     * 将对象装成map形式
     *
     * @param src
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static Map toMap(Object src) {
        return BeanMap.create(src);
    }

    /**
     * 将map 转为 bean
     */
    public static <T> T toBean(Map<String, Object> beanMap, Class<T> valueType) {
        T bean = BeanUtils.newInstance(valueType);
        PropertyDescriptor[] beanPds = getPropertyDescriptors(valueType);
        for (PropertyDescriptor propDescriptor : beanPds) {
            String propName = propDescriptor.getName();
            // 过滤class属性
            if (propName.equals("class")) {
                continue;
            }
            if (beanMap.containsKey(propName)) {
                Method writeMethod = propDescriptor.getWriteMethod();
                if (null == writeMethod) {
                    continue;
                }
                Object value = beanMap.get(propName);
                if (!writeMethod.isAccessible()) {
                    writeMethod.setAccessible(true);
                }
                try {
                    writeMethod.invoke(bean, value);
                } catch (Throwable e) {
                    throw new RuntimeException("Could not set property '" + propName + "' to bean", e);
                }
            }
        }
        return bean;
    }

    /**
     * 将扁平列表拼装成层级树。
     *
     * @param list
     * @param getId
     * @param getParentId
     * @param setChildren
     * @param <T>
     * @return
     */
    public static <T> List<T> buildTree(List<T> list, Function<T, ?> getId, Function<T, ?> getParentId, BiConsumer<T, List<T>> setChildren) {
        List<Object> idList = new ArrayList();
        Map<Object, List<T>> childrenMap = new HashMap<>();
        for (T item : list) {
            Object parentId = getParentId.apply(item);
            if (parentId == null) {
                parentId = "";
            }
            childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(item);

            Object id = getId.apply(item);
            setChildren.accept(item, childrenMap.computeIfAbsent(id, k -> new ArrayList<>()));
            idList.add(id);
        }
        return list.stream().filter(item -> !idList.contains(getParentId.apply(item))).collect(Collectors.toList());
    }

    public static <T, R> List<R> convertList(List<T> list, Function<T, R> converter) {
        if (list == null) {
            return null;
        }
        List<R> newList = new ArrayList(list.size());
        list.forEach(item -> newList.add(converter.apply(item)));
        return newList;
    }

    public static <T, R> List<R> convertList(List<T> list, Class<R> targetType) {
        if (list == null) {
            return null;
        }
        List<R> newList = new ArrayList(list.size());
        list.forEach(item -> newList.add(convert(item, targetType)));
        return newList;
    }

    public static <K, V, T> Map<K, V> buildMap(List<T> list, Function<T, K> key, Function<T, V> value) {
        Map<K, V> m = new HashMap<>(list.size());
        list.forEach(i -> m.put(key.apply(i), value.apply(i)));
        return m;
    }
}
